﻿/*
   Copyright 2013 Dmitry Bratus

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AopAlliance.Intercept;
using Flower.Logging;
using Flower.Security;
using Flower.Directory.Util;
using System.Transactions;
using Flower.Services;
using Flower.Services.Data;

namespace Flower.Directory.Host.Default.Advices
{
    internal class ProcessDispatcherNotifier : IMethodInterceptor
    {
        private static readonly Log Log = LogManager.CreateLog(typeof(ProcessDispatcherNotifier).Name);

        private readonly Dictionary<string, Action<IMethodInvocation>> _methodCallHandlers;
        private readonly Lazy<string> _activeProcessorsFolderId;

        private volatile IDirectory _directory;
        private volatile IProcessesDispatcher _dispatcher;
        private volatile bool _dispatcherObtained;

        public ProcessDispatcherNotifier()
        {
            _methodCallHandlers = new Dictionary<string, Action<IMethodInvocation>>
            {
                { "Save", OnSave },
                { "Move", OnMove },
                { "Remove", OnRemove }
            };
            _activeProcessorsFolderId = new Lazy<string>(() => GetFolderId("/Processors/Active"));
        }

        private string GetFolderId(string path)
        {
            using (new AuthorizationOverride("Administrator"))
            {
                DirectoryEntryInfo folder = _directory.GetInfo(path);

                if (folder == null)
                {
                    Log.Warning(string.Format("Folder {0} found.", folder));
                    return null;
                }

                return folder.Id;
            }
        }

        public object Invoke(IMethodInvocation invocation)
        {
            object result = invocation.Proceed();

            EnsureDispatcherAndDirectory(invocation);

            if (_directory != null && _dispatcher != null)
            {
                try
                {
                    Action<IMethodInvocation> handler;

                    if (_methodCallHandlers.TryGetValue(invocation.Method.Name, out handler))
                    {
                        var trn = Transaction.Current;

                        if (trn == null)
                        {
                            handler(invocation);
                        }
                        else
                        {
                            trn.TransactionCompleted += (sender, args) =>
                            {
                                if (args.Transaction.TransactionInformation.Status == TransactionStatus.Committed)
                                {
                                    try
                                    {
                                        handler(invocation);
                                    }
                                    catch (Exception ex)
                                    {
                                        Log.Warning("Failed to notify the processes dispatcher.", ex);
                                    }
                                }
                            };
                        }
                    }
                }
                catch (Exception ex)
                {
                    Log.Warning("Failed to notify the processes dispatcher.", ex);
                }
            }

            return result;
        }

        private void EnsureDispatcherAndDirectory(IMethodInvocation invocation)
        {
            if (!_dispatcherObtained)
            {
                lock (this)
                {
                    if (!_dispatcherObtained)
                    {
                        var directory = invocation.Target as IDirectory;

                        if (directory != null)
                        {
                            _directory = directory;
                        }
                        else
                        {
                            Log.Error(string.Format("The object wrapped by ProcessDispatcherNotifier is not IDirectory ({0})", invocation.Target));
                        }

                        var holder = invocation.Target as IProcessesDispatcherHolder;

                        if (holder != null)
                        {
                            _dispatcher = holder.ProcessesDispatcher;
                        }
                        else
                        {
                            Log.Error(string.Format("The object wrapped by ProcessDispatcherNotifier is not IProcessesDispatcherHolder ({0})", invocation.Target));
                        }

                        _dispatcherObtained = true;
                    }
                }
            }
        }

        private void OnSave(IMethodInvocation invocation)
        {
            var parent = invocation.Arguments[0] as string;
            var entry = invocation.Arguments[1] as DirectoryEntry;

            if (parent == null)
            {
                var wf = entry as Workflow;

                if (wf != null)
                {
                    _dispatcher.RefreshWorkflow(_directory.GetPathById(wf.Id));
                }
            }
            else
            {
                if (DirectoryUtil.IsId(parent))
                {
                    if (parent == _activeProcessorsFolderId.Value)
                    {
                        _dispatcher.RefreshProcessor(entry.Name);
                    }
                }
                else
                {
                    if (parent == "/Processors/Active")
                    {
                        _dispatcher.RefreshProcessor(entry.Name);
                    }
                    else if (parent.StartsWith("/Processors/"))
                    {
                        string[] pathParts = DirectoryUtil.SplitPath(parent);

                        if (pathParts.Length == 3 && pathParts[2] != "Suspended")
                        {
                            string processorName = pathParts[1];
                            _dispatcher.ProcessStatusChanged(ParseProcessStatus(pathParts[2]), processorName);
                        }
                    }
                    else if (parent.StartsWith("/Workflows/"))
                    {
                        _dispatcher.RefreshWorkflow(parent + entry.Name);
                    }
                }
            }
        }

        private void OnMove(IMethodInvocation invocation)
        {
            var where = invocation.Arguments[1] as string;

            if (where.StartsWith("/Processors/"))
            {
                string[] pathParts = DirectoryUtil.SplitPath(where);

                if (pathParts.Length == 3 && pathParts[2] != "Suspended")
                {
                    string processorName = pathParts[1];
                    _dispatcher.ProcessStatusChanged(ParseProcessStatus(pathParts[2]), processorName);
                }
            }
        }

        private void OnRemove(IMethodInvocation invocation)
        {
            var what = invocation.Arguments[0] as string;

            if (what.StartsWith("/Processors/Active/"))
            {
                string[] pathParts = DirectoryUtil.SplitPath(what);

                if (pathParts.Length == 3)
                {
                    _dispatcher.RefreshProcessor(pathParts[2]);
                }
            }
            else if (what.StartsWith("/Workflows/"))
            {
                _dispatcher.RefreshWorkflow(what);
            }
        }

        private static ProcessStatus ParseProcessStatus(string status)
        {
            switch (status)
            {
                case "Pending":
                    return ProcessStatus.Pending;
                case "Running":
                    return ProcessStatus.Running;
                case "Waiting":
                    return ProcessStatus.Waiting;
                case "Finished":
                    return ProcessStatus.Finished;
                case "Broken":
                    return ProcessStatus.Broken;
                case "Migrated":
                    return ProcessStatus.Migrated;
                default:
                    throw new ArgumentException(string.Format("Unknown process status {0}.", status), "status");
            }
        }
    }
}
